#include "Lexer.h"

namespace FlooP
{
	Lexer::Lexer (FileInput& file)
	:
		m_file		(file),
		m_line		(1)
	{
		// Fill up our buffers and reset the character offset
		consume ();
		consume ();
		m_character = 0;

		// Add keywords
		m_keywords.insert (std::pair<std::string, Token::Type>("ABORT",		Token::ABORT));
		m_keywords.insert (std::pair<std::string, Token::Type>("AND",		Token::AND));
		m_keywords.insert (std::pair<std::string, Token::Type>("AT",		Token::AT));
		m_keywords.insert (std::pair<std::string, Token::Type>("BEGIN",		Token::BEGIN));
		m_keywords.insert (std::pair<std::string, Token::Type>("BLOCK",		Token::BLOCK));
		m_keywords.insert (std::pair<std::string, Token::Type>("CELL",		Token::CELL));
		m_keywords.insert (std::pair<std::string, Token::Type>("DEFINE",	Token::DEFINE));
		m_keywords.insert (std::pair<std::string, Token::Type>("END",		Token::END));
		m_keywords.insert (std::pair<std::string, Token::Type>("IF",		Token::IF));
		m_keywords.insert (std::pair<std::string, Token::Type>("LOOP",		Token::LOOP));
		m_keywords.insert (std::pair<std::string, Token::Type>("MOST",		Token::MOST));
		m_keywords.insert (std::pair<std::string, Token::Type>("MU-LOOP",	Token::MU_LOOP));
		m_keywords.insert (std::pair<std::string, Token::Type>("NO",		Token::NO));
		m_keywords.insert (std::pair<std::string, Token::Type>("OR",		Token::OR));
		m_keywords.insert (std::pair<std::string, Token::Type>("OUTPUT",	Token::OUTPUT));
		m_keywords.insert (std::pair<std::string, Token::Type>("PROCEDURE", Token::PROCEDURE));
		m_keywords.insert (std::pair<std::string, Token::Type>("QUIT",		Token::QUIT));
		m_keywords.insert (std::pair<std::string, Token::Type>("THEN",		Token::THEN));
		m_keywords.insert (std::pair<std::string, Token::Type>("TIMES",		Token::TIMES));
		m_keywords.insert (std::pair<std::string, Token::Type>("YES",		Token::YES));
	}

	Token Lexer::read_token ()
	{
		Token token;

		while (token.type () == Token::NONE) {
			switch (m_buffer[0])
			{
				case '(': token = Token (Token::LEFT_PARENTHESES); break;
				case ')': token = Token (Token::RIGHT_PARENTHESES); break;
				case '[': token = Token (Token::LEFT_BRACKET); break;
				case ']': token = Token (Token::RIGHT_BRACKET); break;
				case '{': token = Token (Token::LEFT_BRACE); break;
				case '}': token = Token (Token::RIGHT_BRACE); break;

				case ':': token = Token (Token::COLON); break;
				case ';': token = Token (Token::SEMICOLON); break;

				case ',': token = Token (Token::COMMA); break;
				case '.': token = Token (Token::DOT); break;

				case '=': token = Token (Token::EQUALS); break;
				case '+': token = Token (Token::PLUS); break;
				case '*': token = Token (Token::STAR); break;

				case '>': token = Token (Token::GREATER_THAN); break;

				case '<': 
				{
					switch (m_buffer[1]) 
					{
						case '=': 
						{
							token = Token (Token::ARROW); 
							consume (); 
						} 
						break;

						default : token = Token (Token::LESS_THAN); break;
					}
				}
				break;

				case '"':
				{
					consume ();
					token = Token (Token::STRING, get_string ());
				}
				break;

				case ' ':  consume (); break;
				case '\t': consume (); break;
				case '\r': consume (); break;
				case '\n': 
				{
					if (m_file.is_eof ()) {
						token = Token (Token::END_OF_FILE);
					} else {
						consume ();
						m_line++;
						m_character = 0;
					}
				}
				break;

				default:
				{
					if (is_digit (m_buffer[0])) {
						token = Token (Token::NUMBER, get_number ());
					} else if (is_alpha (m_buffer[0])) {
						std::string str = get_identifier ();

						auto iterator = m_keywords.find (str);
						if (iterator != m_keywords.end ()) {
							token = Token (iterator->second, str);
						} else {
							token = Token (Token::IDENTIFIER, str);
						}
					} else {
						throw Error (m_line, m_character, "Lexer : Unexpected character '%c'.", m_buffer[0]);
					}

					// We don't need to consume, so we just return the token now
					return token;
				}
			}
		}

		consume ();
		return token;
	}

	uint32_t Lexer::line () const
	{
		return m_line;
	}

	uint32_t Lexer::character () const
	{
		return m_character;
	}

	uint32_t Lexer::get_number ()
	{
		std::string buffer = "";

		while (is_digit (m_buffer[0])) {
			buffer.push_back (m_buffer[0]);
			consume ();
		}

		int ret = atoi (buffer.c_str ());
		
		if (ret == HUGE_VAL) {
			throw Error (m_line, m_character, "Lexer : Numerical value is out of range.");
		}

		return static_cast<uint32_t> (ret);
	}

	std::string Lexer::get_string ()
	{
		std::string buffer = "";

		// Save these values so we can warn the user if the string does not terminate
		uint32_t line = m_line;
		uint32_t character = m_character;

		while (m_buffer[0] != '"') {
			buffer.push_back (m_buffer[0]);
			consume ();

			if (m_file.is_eof ()) {
				throw Error (line, character, "Lexer : String has not been terminated. Expected '\"'");
			}
		}

		return buffer;
	}

	std::string Lexer::get_identifier ()
	{
		std::string buffer = "";

		// Identifiers also allow hyphens and question marks
		while (is_alpha (m_buffer[0]) || is_digit (m_buffer[0]) || m_buffer[0] == '-' || m_buffer[0] == '?') {
			buffer.push_back (m_buffer[0]);
			consume ();
		}

		return buffer;
	}

	void Lexer::consume ()
	{
		m_buffer[0] = m_buffer[1];

		if (m_file.is_eof ())
		{
			m_buffer[0] = '\n';
			m_buffer[1] = '\n';
		} else {
			m_buffer[1] = m_file.read ();
			m_character++;
		}
	}

	bool Lexer::is_digit (int8_t character) const
	{
		return ((character >= '0') && (character <= '9'));
	}

	bool Lexer::is_alpha (int8_t character) const
	{
		return (((character >= 'a') && (character <= 'z')) || ((character >= 'A') && (character <= 'Z')));
	}
}